#!/usr/bin/env python
# _*_ coding: utf-8 _*_
"""
@author: caimengzhi
@license: (C) Copyright 2013-2017.
@contact: 610658552@qq.com
@software: pycharm 2017.02
@project: luxffy
@file: main.py
@time: 18-3-2 上午9:15
@desc:
"""
import os
import sys
import json
import socket
import hashlib
import subprocess
import configparser
from conf import settings
from multiprocessing import Process, Queue
from threading import Thread

class FTPServer(object):
    MSG_SIZE = 1024

    STATUS_CODE = {
        200: "Passed authentication!",
        201: "Wrong username or password!",
        300: "File does not exist !",
        301: "File exist , and this msg include the file size- !",
        302: "This msg include the msg size!",
        350: "Dir changed !",
        351: "Dir doesn't exist !",
        401: "File exist ,ready to re-send !",
        402: "File exist ,but file size doesn't match!",
        500: "ftp server max connected equal MAX"
    }

    def __init__(self,management_instance):
        self.management_instance = management_instance
        self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 地址重用
        self.sock.bind((settings.HOST,settings.PORT))
        self.sock.listen(settings.MAX_SOCKET_LISTEN)
        self.accounts = self.load_accounts()
        self.user_obj = None
        self.q_client_conn_num = Queue(settings.MAX_FTP_CONN)   # the numbers of client connect server

    def load_accounts(self):
        """loads all accounts """
        config_obj = configparser.ConfigParser()
        config_obj.read(settings.ACCOUNT_FILE)

        # print(config_obj.sections())
        return config_obj

    # def run(self):
    #     pass

    def run_forever(self):
        print("start ftp server on %s:%s".center(50, "-") % (settings.HOST, settings.PORT))
        # q = Queue(settings.MAX_FTP_CONN)  # ftp client max connect

        while True:
            print("wait connect from client")
            self.request, self.addr = self.sock.accept()
            print("got a new connection from %s....." % (self.addr,))
            # p = Process(target=self.handle)
            # p.start()
            t = Thread(target=self.handle)
            t.start()
            self.q_client_conn_num.put(1)
            print("q_client_conn_num add one")
        # self.request.close()

    # def run_forever(self):
    #     print("start ftp server on %s:%s".center(50, "-") % (settings.HOST, settings.PORT))
    #     while True:
    #         # self.request, self.addr = self.sock.accept()
    #         # print("got a new connection from %s....." % (self.addr,))
    #         # self.handle()
    #         self.request, self.addr = self.sock.accept()
    #         print("got a new connection from %s....." % (self.addr,))
    #         try:
    #             self.handle()
    #         except Exception as e:
    #             print("Error happend with client,close connection.", e)
    #             self.request.close()

    def handle(self):
        while True:
            print("server wait data from client")
            rev_data = self.request.recv(self.MSG_SIZE)
            print("rev_data = ",rev_data)
            if not rev_data:
                # print("connection %s is lost.... " % (self.addr))
                # self.q_client_conn_num.get()
                print(self.q_client_conn_num.qsize())
                if not self.q_client_conn_num.empty():
                    self.q_client_conn_num.get()
                # self.request.close()

                del self.request, self.addr
                break
            print("current connect numbers is  ", self.q_client_conn_num.qsize())
            data = json.loads(rev_data.decode("utf-8"))
            # print("rev data from client = ",data)
            action_type = data.get("action_type")
            # print("action_type = ",action_type)
            if action_type:
                if hasattr(self,"_%s" % action_type):
                    getattr(self,"_%s" % action_type)(data)
            else:
                print("invalid command")
            # self.q_client_conn_num.get()

    def hash_md5(self,password):
        md5_obj = hashlib.md5()
        md5_obj.update(password.encode())
        return md5_obj.hexdigest()

    def send_respone(self,status_code, *args, **kwargs):
        """
        :param status_code:
        :param args:
        :param kwargs:
        :return:
        """
        data = kwargs
        data["status_code"] = status_code
        data["status_msg"] = self.STATUS_CODE[status_code]
        data['fill'] = ""
        bytes_data = json.dumps(data).encode('utf-8')
        if len(bytes_data) < self.MSG_SIZE:
            data['fill'] = data['fill'].zfill(self.MSG_SIZE - len(bytes_data))
            bytes_data = json.dumps(data).encode('utf-8')

        self.request.send(bytes_data)

    def _auth(self,data):
        # print("auth data = ",data)
        if self.authenticate(data.get('username'), data.get('password')):
            self.send_respone(status_code=200)
        else:
            if self.q_client_conn_num.qsize() == settings.MAX_FTP_CONN:  # the client from server of number is max
                self.send_respone(status_code=500)
            self.send_respone(status_code=201)

    def authenticate(self, username, password):
        if username in self.accounts:
            md5_password = self.hash_md5(password)
            if md5_password == self.accounts[username]["password"]:
                # print("passed authentication...")
                self.user_obj = self.accounts[username]
                self.user_obj['home'] = os.path.join(settings.USER_HOME_DIR, username)
                self.user_current_dir = self.user_obj['home']
                # print("self.user_obj = ",self.user_obj)
                # print("current user: %s home direction is %s" % (username, self.user_obj['home']))
                return True
            else:
                print("wrong username or password")
                return False

        else:
            print("%s is not exist." % username)
            return False

    def _get(self,data):
        """
        client download file through this method
        1. get filename
        2. judge the file is or not exist
            2.1 filename is exist
                2.1.1 send status_code and file size
                2.1.2 send file to client
            2.2 filename is not exist
                2.1 send status_code
        :param data: filename and other ex. 1.mp4 cut from client command get 1.mp4
        :return:
        """
        print(" get file from client ".center(50, "-"))
        filename = data.get('filename')
        full_path = os.path.join(self.user_obj["home"], filename)
        print("filename = ",filename)
        print("full_path = ",full_path)
        if os.path.isfile(full_path):
            filesize = os.stat(full_path).st_size
            self.send_respone(status_code=301, file_size=filesize)
            print("I am read to send file to client.")
            with open(full_path, "rb") as fd:   # rb mode send to client
                for line in fd:
                    self.request.send(line)
            print("the %s file send done....", full_path)
        else:
            self.send_respone(status_code=300)

    def _ls(self, data):
        print(data)
        cmd_obj = subprocess.Popen('%s %s' % (settings.list, self.user_current_dir),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
        stdout = cmd_obj.stdout.read()
        stderr = cmd_obj.stderr.read()

        cmd_result = stdout + stderr
        if not cmd_result: # current dir is empyt
            # cmd_result = b'current dir has no file at all'
            cmd_result = 'current dir has no file at all'.encode("utf-8")
        self.send_respone(302,cmd_result_size=len(cmd_result))
        self.request.sendall(cmd_result)

    def _cd(self, data):
        print(" cd ".center(50,"-"))
        print("data = ",data)
        target_dir = data.get('target_dir')
        full_path = os.path.abspath(os.path.join(self.user_current_dir,target_dir))
        print("full_path = ",full_path)

        if os.path.isdir(full_path):
            self.user_current_dir = full_path
            relative_current_dir = self.user_current_dir.replace(self.user_obj['home'],"")
            self.send_respone(status_code=350,current_dir=relative_current_dir)
        else:
            self.send_respone(status_code=351)  # "Dir doesn't exist !",
